#include "Config.h"
#include "include/PowerLibraryEngine.h"

#include "LinuxDevice.h"

#ifdef __COMPILE_LINUX__

#ifdef __COMPILE_WITH_OPENGL__
    #include "Drivers/OpenGL/OpenGLDriver.h"
    #include "Drivers/OpenGL/OpenGLDriverLinux.h"
    #include "Drivers/OpenGL/OpenGLTextureManager.h"
    #include <GL/glx.h>
    #include <GL/gl.h>

    #include <X11/X.h>
    #include <X11/keysym.h>
    #include <stdio.h>
    #include <stdlib.h>
#endif

static int snglBuf[] = {GLX_RGBA, GLX_DEPTH_SIZE, 16, None};
static int dblBuf[]  = {GLX_RGBA, GLX_DEPTH_SIZE, 16, GLX_DOUBLEBUFFER, None};

bool CLinuxDevice::CreatePLEWindow()
{
    Colormap cmap;
    XSetWindowAttributes swa;
    GLXContext cx;
    GLboolean needRedraw = GL_FALSE, recalcModelView = GL_TRUE;
    s32 dummy;

	KeyMap.push_back(SKeyMap(XK_BackSpace, KEY_BACK));
	KeyMap.push_back(SKeyMap(XK_Tab, KEY_TAB));
	KeyMap.push_back(SKeyMap(XK_Linefeed, 0)); // ???
	KeyMap.push_back(SKeyMap(XK_Clear, KEY_CLEAR));
	KeyMap.push_back(SKeyMap(XK_Return, KEY_RETURN));
	KeyMap.push_back(SKeyMap(XK_Pause, KEY_PAUSE));
	KeyMap.push_back(SKeyMap(XK_Scroll_Lock, KEY_SCROLL));
	KeyMap.push_back(SKeyMap(XK_Sys_Req, 0)); // ???
	KeyMap.push_back(SKeyMap(XK_Escape, KEY_ESCAPE));
	KeyMap.push_back(SKeyMap(XK_Insert, KEY_INSERT));
	KeyMap.push_back(SKeyMap(XK_Delete, KEY_DELETE));
	KeyMap.push_back(SKeyMap(XK_Home, KEY_HOME));
	KeyMap.push_back(SKeyMap(XK_Left, KEY_LEFT));
	KeyMap.push_back(SKeyMap(XK_Up, KEY_UP));
	KeyMap.push_back(SKeyMap(XK_Right, KEY_RIGHT));
	KeyMap.push_back(SKeyMap(XK_Down, KEY_DOWN));
	KeyMap.push_back(SKeyMap(XK_Prior, KEY_PRIOR));
	KeyMap.push_back(SKeyMap(XK_Page_Up, KEY_PRIOR));
	KeyMap.push_back(SKeyMap(XK_Next, KEY_NEXT));
	KeyMap.push_back(SKeyMap(XK_Page_Down, KEY_NEXT));
	KeyMap.push_back(SKeyMap(XK_End, KEY_END));
	KeyMap.push_back(SKeyMap(XK_Begin, KEY_HOME));
	KeyMap.push_back(SKeyMap(XK_Num_Lock, KEY_NUMLOCK));
	KeyMap.push_back(SKeyMap(XK_KP_Space, KEY_SPACE));
	KeyMap.push_back(SKeyMap(XK_KP_Tab, KEY_TAB));
	KeyMap.push_back(SKeyMap(XK_KP_Enter, KEY_RETURN));
	KeyMap.push_back(SKeyMap(XK_KP_F1, KEY_F1));
	KeyMap.push_back(SKeyMap(XK_KP_F2, KEY_F2));
	KeyMap.push_back(SKeyMap(XK_KP_F3, KEY_F3));
	KeyMap.push_back(SKeyMap(XK_KP_F4, KEY_F4));
	KeyMap.push_back(SKeyMap(XK_KP_Home, KEY_HOME));
	KeyMap.push_back(SKeyMap(XK_KP_Left, KEY_LEFT));
	KeyMap.push_back(SKeyMap(XK_KP_Up, KEY_UP));
	KeyMap.push_back(SKeyMap(XK_KP_Right, KEY_RIGHT));
	KeyMap.push_back(SKeyMap(XK_KP_Down, KEY_DOWN));
	KeyMap.push_back(SKeyMap(XK_Print, KEY_PRINT));
	KeyMap.push_back(SKeyMap(XK_KP_Prior, KEY_PRIOR));
	KeyMap.push_back(SKeyMap(XK_KP_Page_Up, KEY_PRIOR));
	KeyMap.push_back(SKeyMap(XK_KP_Next, KEY_NEXT));
	KeyMap.push_back(SKeyMap(XK_KP_Page_Down, KEY_NEXT));
	KeyMap.push_back(SKeyMap(XK_KP_End, KEY_END));
	KeyMap.push_back(SKeyMap(XK_KP_Begin, KEY_HOME));
	KeyMap.push_back(SKeyMap(XK_KP_Insert, KEY_INSERT));
	KeyMap.push_back(SKeyMap(XK_KP_Delete, KEY_DELETE));
	KeyMap.push_back(SKeyMap(XK_KP_Equal, 0)); // ???
	KeyMap.push_back(SKeyMap(XK_KP_Multiply, KEY_MULTIPLY));
	KeyMap.push_back(SKeyMap(XK_KP_Add, KEY_ADD));
	KeyMap.push_back(SKeyMap(XK_KP_Separator, KEY_SEPARATOR));
	KeyMap.push_back(SKeyMap(XK_KP_Subtract, KEY_SUBTRACT));
	KeyMap.push_back(SKeyMap(XK_KP_Decimal, KEY_DECIMAL));
	KeyMap.push_back(SKeyMap(XK_KP_Divide, KEY_DIVIDE));
	KeyMap.push_back(SKeyMap(XK_KP_0, KEY_0));
	KeyMap.push_back(SKeyMap(XK_KP_1, KEY_1));
	KeyMap.push_back(SKeyMap(XK_KP_2, KEY_2));
	KeyMap.push_back(SKeyMap(XK_KP_3, KEY_3));
	KeyMap.push_back(SKeyMap(XK_KP_4, KEY_4));
	KeyMap.push_back(SKeyMap(XK_KP_5, KEY_5));
	KeyMap.push_back(SKeyMap(XK_KP_6, KEY_6));
	KeyMap.push_back(SKeyMap(XK_KP_7, KEY_7));
	KeyMap.push_back(SKeyMap(XK_KP_8, KEY_8));
	KeyMap.push_back(SKeyMap(XK_KP_9, KEY_9));
	KeyMap.push_back(SKeyMap(XK_F1, KEY_F1));
	KeyMap.push_back(SKeyMap(XK_F2, KEY_F2));
	KeyMap.push_back(SKeyMap(XK_F3, KEY_F3));
	KeyMap.push_back(SKeyMap(XK_F4, KEY_F4));
	KeyMap.push_back(SKeyMap(XK_F5, KEY_F5));
	KeyMap.push_back(SKeyMap(XK_F6, KEY_F6));
	KeyMap.push_back(SKeyMap(XK_F7, KEY_F7));
	KeyMap.push_back(SKeyMap(XK_F8, KEY_F8));
	KeyMap.push_back(SKeyMap(XK_F9, KEY_F9));
	KeyMap.push_back(SKeyMap(XK_F10, KEY_F10));
	KeyMap.push_back(SKeyMap(XK_F11, KEY_F11));
	KeyMap.push_back(SKeyMap(XK_F12, KEY_F12));
	KeyMap.push_back(SKeyMap(XK_Shift_L, KEY_LSHIFT));
	KeyMap.push_back(SKeyMap(XK_Shift_R, KEY_RSHIFT));
	KeyMap.push_back(SKeyMap(XK_Control_L, KEY_LCONTROL));
	KeyMap.push_back(SKeyMap(XK_Control_R, KEY_RCONTROL));
	KeyMap.push_back(SKeyMap(XK_Caps_Lock, KEY_CAPITAL));
	KeyMap.push_back(SKeyMap(XK_Shift_Lock, KEY_CAPITAL));
	KeyMap.push_back(SKeyMap(XK_Meta_L, KEY_LWIN));
	KeyMap.push_back(SKeyMap(XK_Meta_R, KEY_RWIN));
	KeyMap.push_back(SKeyMap(XK_Alt_L, KEY_LMENU));
	KeyMap.push_back(SKeyMap(XK_Alt_R, KEY_RMENU));
	KeyMap.push_back(SKeyMap(XK_ISO_Level3_Shift, KEY_RMENU));
	KeyMap.push_back(SKeyMap(XK_Menu, KEY_MENU));
	KeyMap.push_back(SKeyMap(XK_space, KEY_SPACE));
	KeyMap.push_back(SKeyMap(XK_exclam, 0)); //?
	KeyMap.push_back(SKeyMap(XK_quotedbl, 0)); //?
	KeyMap.push_back(SKeyMap(XK_section, 0)); //?
	KeyMap.push_back(SKeyMap(XK_numbersign, 0)); //?
	KeyMap.push_back(SKeyMap(XK_dollar, 0)); //?
	KeyMap.push_back(SKeyMap(XK_percent, 0)); //?
	KeyMap.push_back(SKeyMap(XK_ampersand, 0)); //?
	KeyMap.push_back(SKeyMap(XK_apostrophe, 0)); //?
	KeyMap.push_back(SKeyMap(XK_parenleft, 0)); //?
	KeyMap.push_back(SKeyMap(XK_parenright, 0)); //?
	KeyMap.push_back(SKeyMap(XK_asterisk, 0)); //?
	KeyMap.push_back(SKeyMap(XK_plus, KEY_PLUS)); //?
	KeyMap.push_back(SKeyMap(XK_comma, KEY_COMMA)); //?
	KeyMap.push_back(SKeyMap(XK_minus, KEY_MINUS)); //?
	KeyMap.push_back(SKeyMap(XK_period, KEY_PERIOD)); //?
	KeyMap.push_back(SKeyMap(XK_slash, 0)); //?
	KeyMap.push_back(SKeyMap(XK_0, KEY_0));
	KeyMap.push_back(SKeyMap(XK_1, KEY_1));
	KeyMap.push_back(SKeyMap(XK_2, KEY_2));
	KeyMap.push_back(SKeyMap(XK_3, KEY_3));
	KeyMap.push_back(SKeyMap(XK_4, KEY_4));
	KeyMap.push_back(SKeyMap(XK_5, KEY_5));
	KeyMap.push_back(SKeyMap(XK_6, KEY_6));
	KeyMap.push_back(SKeyMap(XK_7, KEY_7));
	KeyMap.push_back(SKeyMap(XK_8, KEY_8));
	KeyMap.push_back(SKeyMap(XK_9, KEY_9));
	KeyMap.push_back(SKeyMap(XK_colon, 0)); //?
	KeyMap.push_back(SKeyMap(XK_semicolon, 0)); //?
	KeyMap.push_back(SKeyMap(XK_less, 0)); //?
	KeyMap.push_back(SKeyMap(XK_equal, 0)); //?
	KeyMap.push_back(SKeyMap(XK_greater, 0)); //?
	KeyMap.push_back(SKeyMap(XK_question, 0)); //?
	KeyMap.push_back(SKeyMap(XK_at, 0)); //?
	KeyMap.push_back(SKeyMap(XK_mu, 0)); //?
	KeyMap.push_back(SKeyMap(XK_EuroSign, 0)); //?
	KeyMap.push_back(SKeyMap(XK_A, KEY_A));
	KeyMap.push_back(SKeyMap(XK_B, KEY_B));
	KeyMap.push_back(SKeyMap(XK_C, KEY_C));
	KeyMap.push_back(SKeyMap(XK_D, KEY_D));
	KeyMap.push_back(SKeyMap(XK_E, KEY_E));
	KeyMap.push_back(SKeyMap(XK_F, KEY_F));
	KeyMap.push_back(SKeyMap(XK_G, KEY_G));
	KeyMap.push_back(SKeyMap(XK_H, KEY_H));
	KeyMap.push_back(SKeyMap(XK_I, KEY_I));
	KeyMap.push_back(SKeyMap(XK_J, KEY_J));
	KeyMap.push_back(SKeyMap(XK_K, KEY_K));
	KeyMap.push_back(SKeyMap(XK_L, KEY_L));
	KeyMap.push_back(SKeyMap(XK_M, KEY_M));
	KeyMap.push_back(SKeyMap(XK_N, KEY_N));
	KeyMap.push_back(SKeyMap(XK_O, KEY_O));
	KeyMap.push_back(SKeyMap(XK_P, KEY_P));
	KeyMap.push_back(SKeyMap(XK_Q, KEY_Q));
	KeyMap.push_back(SKeyMap(XK_R, KEY_R));
	KeyMap.push_back(SKeyMap(XK_S, KEY_S));
	KeyMap.push_back(SKeyMap(XK_T, KEY_T));
	KeyMap.push_back(SKeyMap(XK_U, KEY_U));
	KeyMap.push_back(SKeyMap(XK_V, KEY_V));
	KeyMap.push_back(SKeyMap(XK_W, KEY_W));
	KeyMap.push_back(SKeyMap(XK_X, KEY_X));
	KeyMap.push_back(SKeyMap(XK_Y, KEY_Y));
	KeyMap.push_back(SKeyMap(XK_Z, KEY_Z));
	KeyMap.push_back(SKeyMap(XK_Adiaeresis, 0)); //?
	KeyMap.push_back(SKeyMap(XK_Odiaeresis, 0)); //?
	KeyMap.push_back(SKeyMap(XK_Udiaeresis, 0)); //?
	KeyMap.push_back(SKeyMap(XK_bracketleft, 0)); //?
	KeyMap.push_back(SKeyMap(XK_backslash, 0)); //?
	KeyMap.push_back(SKeyMap(XK_bracketright, 0)); //?
	KeyMap.push_back(SKeyMap(XK_asciicircum, 0)); //?
	KeyMap.push_back(SKeyMap(XK_degree, 0)); //?
	KeyMap.push_back(SKeyMap(XK_underscore, 0)); //?
	KeyMap.push_back(SKeyMap(XK_grave, 0)); //?
	KeyMap.push_back(SKeyMap(XK_acute, 0)); //?
	KeyMap.push_back(SKeyMap(XK_quoteleft, 0)); //?
	KeyMap.push_back(SKeyMap(XK_a, KEY_A));
	KeyMap.push_back(SKeyMap(XK_b, KEY_B));
	KeyMap.push_back(SKeyMap(XK_c, KEY_C));
	KeyMap.push_back(SKeyMap(XK_d, KEY_D));
	KeyMap.push_back(SKeyMap(XK_e, KEY_E));
	KeyMap.push_back(SKeyMap(XK_f, KEY_F));
	KeyMap.push_back(SKeyMap(XK_g, KEY_G));
	KeyMap.push_back(SKeyMap(XK_h, KEY_H));
	KeyMap.push_back(SKeyMap(XK_i, KEY_I));
	KeyMap.push_back(SKeyMap(XK_j, KEY_J));
	KeyMap.push_back(SKeyMap(XK_k, KEY_K));
	KeyMap.push_back(SKeyMap(XK_l, KEY_L));
	KeyMap.push_back(SKeyMap(XK_m, KEY_M));
	KeyMap.push_back(SKeyMap(XK_n, KEY_N));
	KeyMap.push_back(SKeyMap(XK_o, KEY_O));
	KeyMap.push_back(SKeyMap(XK_p, KEY_P));
	KeyMap.push_back(SKeyMap(XK_q, KEY_Q));
	KeyMap.push_back(SKeyMap(XK_r, KEY_R));
	KeyMap.push_back(SKeyMap(XK_s, KEY_S));
	KeyMap.push_back(SKeyMap(XK_t, KEY_T));
	KeyMap.push_back(SKeyMap(XK_u, KEY_U));
	KeyMap.push_back(SKeyMap(XK_v, KEY_V));
	KeyMap.push_back(SKeyMap(XK_w, KEY_W));
	KeyMap.push_back(SKeyMap(XK_x, KEY_X));
	KeyMap.push_back(SKeyMap(XK_y, KEY_Y));
	KeyMap.push_back(SKeyMap(XK_z, KEY_Z));
	KeyMap.push_back(SKeyMap(XK_ssharp, 0)); //?
	KeyMap.push_back(SKeyMap(XK_adiaeresis, 0)); //?
	KeyMap.push_back(SKeyMap(XK_odiaeresis, 0)); //?
	KeyMap.push_back(SKeyMap(XK_udiaeresis, 0)); //?

    dpy = XOpenDisplay(NULL);
    if (dpy == NULL)
    {
        pLogDebug << "could not open display";
    }

    if(!glXQueryExtension(dpy, &dummy, &dummy))
        pLogDebug << "X server has no OpenGL GLX extension";


  /*** (4) create an OpenGL rendering context  ***/

   ChoseCorrectVisual();
  /* create an OpenGL rendering context */
  cx = glXCreateContext(dpy, vi, /* no shared dlists */ None, /* direct rendering if possible */ GL_TRUE);
  if (cx == NULL)
    pLogDebug << "could not create rendering context";

  /*** (5) create an X window with the selected visual ***/

  /* create an X colormap since probably not using default visual */
  cmap = XCreateColormap(dpy, RootWindow(dpy, vi->screen), vi->visual, AllocNone);
  swa.colormap = cmap;
  swa.border_pixel = 0;
  swa.event_mask = KeyPressMask    | ExposureMask | ButtonPressMask | StructureNotifyMask;
  win = XCreateWindow(dpy, RootWindow(dpy, vi->screen), 0, 0,
                        drvp->LenX, drvp->LenY, 0, vi->depth, InputOutput, vi->visual,
                      CWBorderPixel | CWColormap | CWEventMask, &swa);

  XSelectInput(dpy, win, KeyPressMask | KeyReleaseMask);

//  XSetStandardProperties(dpy, win, "main", "main", None,
//                         argv, argc, NULL);

    glXMakeCurrent(dpy, win, cx);

    XMapWindow(dpy, win);
    return true;
}

bool CLinuxDevice::ChoseCorrectVisual()
{
   /* find an OpenGL-capable RGB visual with depth buffer */
    dblBuf[2] = 32;
    vi = glXChooseVisual(dpy, DefaultScreen(dpy), dblBuf);
    if (vi == NULL)
    {
        dblBuf[2] = 24;
        vi = glXChooseVisual(dpy, DefaultScreen(dpy), dblBuf);
    }

    if (vi == NULL)
    {
        vi = glXChooseVisual(dpy, DefaultScreen(dpy), snglBuf);
        doubleBuffer = GL_FALSE;
    }

    if (vi == NULL)
    {
        pLogDebug << "no RGB visual with depth buffer";
        return false;
    }

    return true;

//  if(vi->class != TrueColor)
//    pLogDebug("TrueColor visual required for this program");
}

CLinuxDevice::CLinuxDevice(StartDriverParams *DrvParams):CCommonDevice(DrvParams)
{
	SetName("LinuxDevice");
	Close = false;

	drvp = DrvParams;

	text = (wchar_t**)malloc(sizeof(wchar_t*));
	*text = (wchar_t*)malloc(sizeof(wchar_t)*200);

	CreateHelpClasses();
}

CLinuxDevice::~CLinuxDevice()
{
	free (*text);
	free(text);
}

void CLinuxDevice::CreateOpenGLDriver()
{
    COpenGLDriverLinux *Temp = new COpenGLDriverLinux(drvp);
    Temp->EnableOpenGL(dpy,&win,drvp);
    SetVideoDriver(Temp);
    SetTextureManager(new COpenGLTextureManager());
}

stringc CLinuxDevice::GetCaption()
{
	return stringc("");
}

void CLinuxDevice::SetCaption(stringc Title)
{
	XTextProperty txt;

    u32 i=0;
	for (;i<Title.length();i++)
        (*text)[i] = (wchar_t)Title[i];

    (*text)[i] = (wchar_t)0;

	if (Success==XwcTextListToTextProperty(dpy,const_cast<wchar_t**>(text),
				1, XStdICCTextStyle, &txt))

	{
		XSetWMName(dpy, win, &txt);
		XSetWMIconName(dpy, win, &txt);
		XFree(txt.value);
	}
}

bool CLinuxDevice::Update()
{
	CCommonDevice::PreUpdate();

	while (XPending(dpy) > 0 && !Close)
	{
		XEvent event;
		XNextEvent(dpy, &event);


		switch (event.type)
		{
   			case KeyRelease:
				if ((XPending(dpy) > 0) )
				{
					// check for Autorepeat manually
					// We'll do the same as Windows does: Only send KeyPressed
					// So every KeyRelease is a real release
					XEvent next_event;
					XPeekEvent (event.xkey.display, &next_event);
					if ((next_event.type == KeyPress) &&
						(next_event.xkey.keycode == event.xkey.keycode) &&
						(next_event.xkey.time - event.xkey.time) < 2)	// usually same time, but on some systems a difference of 1 is possible
					{
						// Ignore the key release event
						XNextEvent(dpy,&event);
						break;
					}
				}
				// fall-through in case the release should be handled*/
			case KeyPress:
				{
					SKeyMap mp;
					SKeyInput KeyInput;
					char buf[8]={0};
					XLookupString(&event.xkey, buf, sizeof(buf), &mp.X11Key, NULL);

                    int idx=-1;
                    for (u32 i=0;i<KeyMap.size();i++)
                    {
                        if (KeyMap[i].X11Key == mp.X11Key)
                        {
                            idx = i;
                            i = KeyMap.size();
                        }
                    }
//					const s32 idx = KeyMap.binary_search(mp);

					if (idx != -1)
						KeyInput.Key = (EKEY_CODE)KeyMap[idx].Win32Key;
					else
					{
						KeyInput.Key = (EKEY_CODE)0;
					}
					KeyInput.PressedDown = (event.type == KeyPress);
					KeyInput.Char = ((wchar_t*)(buf))[0];
					KeyInput.Control = (event.xkey.state & ControlMask) != 0;
					KeyInput.Shift = (event.xkey.state & ShiftMask) != 0;
					InputManager->NewKey(&KeyInput);
					if (KeyInput.PressedDown)
                        pLogDebug << "CLinuxDevice:: Key: " << KeyInput.Key << ", Char: " << KeyInput.Char << ", Control: " << KeyInput.Control << ", Shift: " << KeyInput.Shift << " is pressed" << endl;
                    else
                        pLogDebug << "CLinuxDevice:: Key: " << KeyInput.Key << ", Char: " << KeyInput.Char << ", Control: " << KeyInput.Control << ", Shift: " << KeyInput.Shift << " is released" << endl;
				}
				break;
			case ClientMessage:
				{
					char *atom = XGetAtomName(dpy, event.xclient.message_type);
					if (*atom == *"WM_DELETE_WINDOW")
					{
					    pLogDebug << "Quit message received\n";
						Close = true;
					}
					else
					{
						// we assume it's a user message
//						irrevent.EventType = irr::EET_USER_EVENT;
//						irrevent.UserEvent.UserData1 = (s32)event.xclient.data.l[0];
//						irrevent.UserEvent.UserData2 = (s32)event.xclient.data.l[1];
//						postEventFromUser(irrevent);
					}
					XFree(atom);
				}
				break;

			default:
				break;
		}
	}

	bool qcommon = CCommonDevice::PostUpdate();

    return !Close || qcommon;
}

#endif
